iT邦幫忙

2021 iThome 鐵人賽

DAY 18
2
Modern Web

三十天成為D3.js v7 好手系列 第 18

Day18-D3 的 Axis( ) & ticks( ) 軸線與刻度

  • 分享至 

  • xImage
  •  

本篇大綱:軸線的組成、建立軸線的必備工具、繪製軸線的API、ticks 刻度、XY 軸範例

講了好久終於進到軸線與刻度了!!!前面的章節看完比例尺之後,接下來就輪到繪製軸線啦~軸線也是圖表中很重要的一部分,幾乎所有圖表都會有X軸跟Y軸
https://ithelp.ithome.com.tw/upload/images/20210930/201349304M7KzQ0UFg.jpg

今天我們就來講講要怎麼繪製軸線吧!

軸線的組成

首先,我們先來看看軸線是由哪些DOM元素組成的。圖表的座標軸其實是一個複雜的結構,一條座標軸包含了

  • < path > 一條直線,標示軸線
  • < line > 一組沿著軸的刻度記號(ticks)
  • < text > 每個刻度記號的標籤

https://ithelp.ithome.com.tw/upload/images/20210930/20134930Z48dxiOHMs.jpg

通常我們會使用 < g > 這個 svg 的結構標籤把整條軸線的元素包在一起,之後對 < g > 元素進行一些操作(例如:移動的時候,就能將整條軸線一起移動。)

https://ithelp.ithome.com.tw/upload/images/20210930/20134930f2oAA94pLI.jpg

建立軸線的必備工具

了解軸線的組成結構後,我們來看看要建立軸線的兩大必備工具

  • DOM 元素 (path、line、text 等等)
    我們必須要有一整組DOM元素,才能繪製出軸線

  • 比例尺
    除了 DOM 元素,也要搭配比例尺去計算出軸線的最大與最小值,才知道軸線要畫多長、刻度要怎麼計算

有了這兩個工具,我們就可以簡單來建立一條軸線了,我們先畫一條X軸試試看

// html
<div class="demo1"></div>

// js
d3.select('.demo1')
  .append('svg')
  .attr("width", 500)
  .attr("height", 200)
  .append('g')

// 設定比例尺
const scale = d3.scaleLinear()
                .domain([0, 100])
                .range([0, 500]);

// 建立軸線
let axis = d3.axisBottom(scale);
d3.select('svg g')
  .call(axis);

建立好的軸線長這樣
https://ithelp.ithome.com.tw/upload/images/20210930/20134930TIyRGKKuzp.jpg

你一定會說:天啊這不是我要的軸線~這不是我要的軸線!為什麼會這樣呢?那是因為 svg 都是從左上方的原點由上往下繪製,因此軸線長這樣很正常。如果想要把軸線左右留點空間、移到下方的位置,我們就要 margintransform 來進行一些設定。

  • 設定 width、height、margin 變數
  • 設定把用 transform 移到 x = margin、y = 高度扣掉marigin*2的地方,再開始繪製軸線
  • 將輸入域的範圍設定成 svg 寬度扣掉 margin*2 (左右兩邊的margin),這樣繪製出來的軸線左右才有距離
let width = 500, 
    height = 200,
    margin = 10;

// 軸線建立
d3.select('.demo1')
  .append('svg')
  .attr("width", width)
  .attr("height", height)
  .append('g')
  .attr('transform', `translate(${margin}, ${height-(margin*2)})`)

const scale = d3.scaleLinear()
                .domain([0, 100])
                .range([0, `${width-(margin*2)}`]);

let axis = d3.axisBottom(scale);
d3.select('svg g')
  .call(axis);

這樣建立出來的軸線就是完美的X軸啦!
https://ithelp.ithome.com.tw/upload/images/20210930/20134930FAOcHXe1iZ.jpg

特別要注意的是,建立軸線時,我們會使用 selection.call( ) 這個方法來呼叫軸線的方法,那D3提供哪幾種繪製軸線的方法呢? 我們一起來看一下

繪製軸線的API

一樣先來看看官方文件~D3 提供了這些方法讓我們能繪製軸線
https://ithelp.ithome.com.tw/upload/images/20210930/20134930QA4hPHxUUg.jpg

axisTop(scale)axisBottom(scale)axisRight(scale)axisLeft(scale)

這四個 API 是主要拿來建立軸線的方法,它們都是帶入 scale比例尺 作為參數,接著根據帶入的比例尺生成一個 axis generator,並包含預設的 ticks刻度設定

Scale 參數

軸線會因為帶入的 scale比例尺 不同,而產生不同的刻度間距,例如:
https://ithelp.ithome.com.tw/upload/images/20210930/2013493098HkiQHN2l.jpg

如果不知道每種 scale 怎麼設定,歡迎看看上一篇的 scale 講解

刻度位置

這四個 API 之所以又分成 Top、Bottom、Right、Left 四種,只是因為生成的 ticks 方向不同~

  • axisTop ⇒ ticks 刻度在軸線上方
    https://ithelp.ithome.com.tw/upload/images/20210930/20134930sP4qfH2IV1.jpg

  • axisBottom ⇒ ticks 刻度在軸線下方
    https://ithelp.ithome.com.tw/upload/images/20210930/20134930IfI4ejokWv.jpg

  • axisRight ⇒ ticks 刻度在軸線右方
    https://ithelp.ithome.com.tw/upload/images/20210930/20134930v2mBKgh3lM.jpg

  • axisLeft ⇒ ticks 刻度在軸線左方
    https://ithelp.ithome.com.tw/upload/images/20210930/20134930THGaEuSNd1.jpg

ticks 刻度

除了預設好的刻度之外,D3也提供了不少讓我們能自訂 ticks 刻度的 API
https://ithelp.ithome.com.tw/upload/images/20210930/20134930MbgqARks2r.jpg

我們能進行的設定包含:

  • 自訂刻度的數量或數值
  • 自訂刻度的呈現文字
  • 自訂刻度的大小、長短、顏色

現在就讓我挑幾個常用的API來講解吧!

axis.ticks( ) => 調整ticks數量

這個方法可以讓我們自行調整刻度的數量。

let scale = d3.scaleLinear().domain([0, 100]).range([0, 500]);

let axis = d3.axisBottom(scale);

axis.ticks(20);

d3.select('svg g')
  .call(axis);

https://ithelp.ithome.com.tw/upload/images/20210930/20134930l0VhRrUyLa.jpg

但要注意的是,即使我們定義好想要的特定刻度數量,如果刻度的值並非完整的數值的話,D3會自行調整刻度數量,讓 ticks value 是漂亮的數字 (例如以5為單位間隔),例如:

let scale = d3.scaleLinear().domain([0, 100]).range([0, 500]);

let axis = d3.axisBottom(scale);

axis.ticks(27); // 改成要27個ticks

d3.select('svg g')
  .call(axis);

出來的結果是一樣的
https://ithelp.ithome.com.tw/upload/images/20210930/20134930rw30oo6oTy.jpg

axis.tickValues( ) => 指定想呈現的 ticks value

這個方法可以用來指定只想呈現哪些 tick values。這個方法超方便,如果我們想呈現某些特別奇怪的刻度,就可以用它來設定~

let scale = d3.scaleLinear().domain([0, 100]).range([0, 500]);

let axis = d3.axisBottom(scale);

axis.tickValues([0, 25, 53, 77, 81, 95]);

d3.select('svg g')
  .call(axis);

https://ithelp.ithome.com.tw/upload/images/20210930/20134930XLNjRvSMDv.jpg

axis.tickFormat( ) => 調整 tick label 的文字

這個也是非常好用的一個API,它可以協助我們調整想要呈現的刻度文字

let scale = d3.scaleLinear().domain([0, 100]).range([0, 500]);

let axis = d3.axisBottom(scale);

axis.tickFormat(function(d) {
		    return d + "元";
		  });

d3.select('svg g')
  .call(axis);

https://ithelp.ithome.com.tw/upload/images/20210930/20134930IsMVhfEznt.jpg

axis.tickSize( )axis.tickSizeInner( )axis.tickSizeOuter( )

再來介紹的這三種方法是用來設定刻度線的長度,D3的刻度線分為 inner 跟 outer 兩種

  • inner ⇒ 軸線內部的刻度線
  • outer ⇒ 軸線最左右兩邊的起始點刻度線

https://ithelp.ithome.com.tw/upload/images/20210930/2013493018thUTZf3W.jpg

而這三種 API 分別能設定

  • tickSize( ) ⇒ 內部+外部刻度線長短一起設定
  • tickSizeInner( ) ⇒ 內部刻度線的長短
  • tickSizeOuter( ) ⇒ 最左右兩側刻度線的長短

預設的刻度線長度是6,但我們可以透過 tickSize 的設定來加長或縮短刻度線的長度,而且會自訂把 tick value label 排到線段之後

let scale = d3.scaleLinear().domain([0, 100]).range([0, 500]);

let axis = d3.axisBottom(scale)
  .tickSize(30);

d3.select('svg g')
	.call(axis);

https://ithelp.ithome.com.tw/upload/images/20210930/20134930c1qdVLL03z.jpg

接著我們也可以進行一些漂亮的設定,像是使用css 跟 tickSize 搭配,去畫出這種虛線段的軸線
https://ithelp.ithome.com.tw/upload/images/20210930/20134930dRbwmXX7AY.jpg

我們先建立軸線


// js
let scale = d3.scaleLinear().domain([0, 100]).range([0, 400]);
let axis = d3.axisRight(scale)
             .tickSize(400); // 把tick的尺寸設定成跟range的最大值一樣,就能得到滿版的軸線

d3.select('.demoDashed')
  .append('svg')
  .attr('width', '500')
  .attr('height', '400')
  .append('g')
  .attr('class', 'dashed')
  .attr('transform', 'translate(20, 20)')
  .call(axis);

接著再設定CSS,將軸線設定為隱藏,刻度線設定為dashed

// css
g.dashed path {
	display: none;
}

g.dashed line {
	stroke-dasharray: 1 1;
}

這樣就完成拉~如果再用一樣的方式設定X軸,就能得到棋盤狀的格線囉!

再來如果我們想單獨設定 tickSizeOuter 或 tickSizeInner 也可以,但就要一起設定 tickPadding,否則軸線就會變成這樣

let scale = d3.scaleLinear().domain([0, 100]).range([0, 500]);

let axis = d3.axisBottom(scale)
  .tickSizeOuter(60);

d3.select('svg g')
	.call(axis);

https://ithelp.ithome.com.tw/upload/images/20210930/20134930P7nf6DsRqq.jpg

看到這張圖,知道接下來的 tickPadding 是做什麼用了嗎?

axis.tickPadding( ) ⇒ 調整刻度線與文字標籤的距離

你沒猜錯!tickPadding( ) 就是用來調整刻度線跟文字標籤的距離,預設的距離是3,但我們可以依照自己的需求去設定

let scale = d3.scaleLinear().domain([0, 100]).range([0, 500]);

let axis = d3.axisBottom(scale)
						 .tickPadding(60);

d3.select('svg g')
	.call(axis);

https://ithelp.ithome.com.tw/upload/images/20210930/20134930J0pbRM2bch.jpg

哇賽好不容易把軸線跟刻度的API 都大致講完了,我們現在來用資料實際畫完整的 XY 軸吧!


XY 軸範例

我們這次要建立的軸線是這樣
https://ithelp.ithome.com.tw/upload/images/20210930/20134930hAv68GaXTp.jpg

由於有兩條軸線,因此兩條軸線都要分別設定要使用的 scale 跟 axis。我們先看到手上擁有的資料

// html
<div class="XY_Axis"></div>

// js
const svg = d3.select(".XY_Axis")
                .append("svg")
                .attr("width", 500)
                .attr("height", 200);

// data
const data = [{x:100, y:20}, {x:18, y:30}, {x:90, Y:250}]

一般來說我們都會拿到這種陣列包物件的資料,裡面帶有多筆數值。我們把要設定X軸的資料跟設定Y軸的資料分別整理出來

// map 資料集
  xData = data.map((i) => i.x); // 預期套資料後會得到 [100, 18, 90] 的陣列
  yData = data.map((i) => i.y); // 預期套資料後會得到 [20, 30, 250] 的陣列

接著我們先來設定 X 軸線

// 設定要給 X 軸用的scale 跟 axis
const xScale = d3.scaleLinear()
              .domain([0, d3.max(xData)])
              .range([20, 480]); // X 軸繪製的viewport起點終點(寬度)

const xAxis = d3.axisBottom(xScale)
              .tickFormat(function (d) {
                //調整標籤樣式
                return `${d} 元`;
              })

// 呼叫繪製x軸、調整x軸位置
const xAxisGroup = svg.append("g")
                    .call(xAxis)
                    .attr("transform", "translate(0,180)")

再來設定 Y 軸線

// 設定要給 Y 軸用的 scale 跟 axis
const yScale = d3.scaleLinear()
                .domain([0, d3.max(yData)])
                .range([20, 180]);  // Y 軸繪製的viewport 起點終點(高度)

const yAxis = d3.axisLeft(yScale)
                .ticks(5)

// 呼叫繪製y軸、調整y軸位置
const yAxisGroup = svg.append("g")
                      .call(yAxis)
                      .attr("transform", "translate(20,0)")

這時候座標軸已經畫出來了,但你會發現Y軸的刻度跟我們平常看到的不一樣,是顛倒的?
https://ithelp.ithome.com.tw/upload/images/20210930/20134930F34bJj62yk.jpg

這是因為軸線是從 svg 左上方的原點,由上往下繪製,所以當我們把 range 設定成 ([20, 180]) 時,Y軸線就是正常的從離原點20px的地方開始由上方往下建立。
https://ithelp.ithome.com.tw/upload/images/20210930/20134930FWsVCnCExh.jpg

為了要讓Y軸的數值能顛倒過來,我們要把 range 的值也顛倒設定

const yScale = d3.scaleLinear()
                .domain([0, d3.max(yData)])
                .range([180, 20]);  // 顛倒

這樣就完成啦!要注意的是,由於 X 跟 Y 軸的長度分別是看 svg 寬度跟 svg 高度,因此它們兩個的 range 範圍設定也會有所不同,不要設定錯囉!
https://ithelp.ithome.com.tw/upload/images/20210930/20134930NB0v8K7nhz.jpg

終於完整介紹完座標軸跟刻度啦!!大家明天見~


Github Page 圖表與 Github 程式碼

最後一樣附上本章的程式碼與圖表 GithubGithub Page,需要的人請自行取用~


上一篇
Day17-D3 的 Scale( ) 比例尺
下一篇
Day19-D3 的 RWD 圖表
系列文
三十天成為D3.js v7 好手30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

1
Global Rachel
iT邦新手 3 級 ‧ 2021-10-04 22:00:16

軸線那張解說圖片也太清楚了吧!(還有顏色搭配)
給你500讚!

金金 iT邦新手 1 級 ‧ 2021-10-05 09:36:50 檢舉

耶~~

我要留言

立即登入留言